/*
 * This file is part of b.AMP.
 *
 * b.AMP is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * b.AMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with b.AMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <bamp/rc.h>
#include <bamp/value.h>

#include <float.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


// Null-terminated name of value types.
const static char* bamp_value_type_string[] = {
    "null",
    "u8", "u16", "u32", "u64",
    "i8", "i16", "i32", "i64",
    "f32", "f64",
    "string"
};

// Number of digits required to convert a value of a given type into a
// string, including sign, decimal point and trailing null byte
// ('\0').
const static size_t bamp_value_type_string_length[] = {
    5,            // Null
    4, 6, 11, 21, // Unsigned
    5, 7, 12, 21, // Signed
    FLT_DIG + 3,  // Float
    DBL_DIG + 3   // Double
};

// Printf-style format for converting between values and strings.
const static char* bamp_value_type_string_format[] = {
    "",                        // Null
    "%hu", "%hu", "%u", "%lu", // Unsigned
    "%hi", "%hi", "%i", "%li", // Signed
    "%f", "%Lf"                // Floating point
};


#define BAMP_VALUE_GET_SET_FUNC(ctype, btype, etype)                    \
    bamp_rc bamp_value_set_##btype(bamp_value* value, ctype btype)      \
    {                                                                   \
        BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);                \
        value->type = BAMP_TYPE_##etype;                                \
        value->value.btype = btype;                                     \
        return BAMP_RC_OK;                                              \
    }                                                                   \
    bamp_rc bamp_value_get_##btype(const bamp_value* value, ctype* btype) \
    {                                                                   \
        BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);                \
        BAMP_RC_ASSERT(btype != NULL, INVALID_ARGUMENT);                \
        BAMP_RC_ASSERT(value->type == BAMP_TYPE_##etype, TYPE_MISMATCH); \
        *btype = value->value.btype;                                    \
        return BAMP_RC_OK;                                              \
    }

bamp_rc bamp_value_type_to_str(bamp_type type, const char** str)
{
    BAMP_RC_ASSERT(str != NULL, INVALID_ARGUMENT);
    *str = bamp_value_type_string[type];
    return BAMP_RC_OK;
}


bamp_rc bamp_value_type_from_str(bamp_type* type, const char* str)
{
    BAMP_RC_ASSERT(type != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(str  != NULL, INVALID_ARGUMENT);

    if      (!strcmp(str, "null"))   *type = BAMP_TYPE_NULL;
    else if (!strcmp(str, "u8"))     *type = BAMP_TYPE_U8;
    else if (!strcmp(str, "u16"))    *type = BAMP_TYPE_U16;
    else if (!strcmp(str, "u32"))    *type = BAMP_TYPE_U32;
    else if (!strcmp(str, "u64"))    *type = BAMP_TYPE_U64;
    else if (!strcmp(str, "i8"))     *type = BAMP_TYPE_I8;
    else if (!strcmp(str, "i16"))    *type = BAMP_TYPE_I16;
    else if (!strcmp(str, "i32"))    *type = BAMP_TYPE_I32;
    else if (!strcmp(str, "i64"))    *type = BAMP_TYPE_I64;
    else if (!strcmp(str, "f32"))    *type = BAMP_TYPE_F32;
    else if (!strcmp(str, "f64"))    *type = BAMP_TYPE_F64;
    else if (!strcmp(str, "string")) *type = BAMP_TYPE_STRING;
    else return BAMP_RC_INVALID_ARGUMENT;

    return BAMP_RC_OK;
}


bamp_rc bamp_value_to_new_str(const bamp_value* value, char** str)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(str   != NULL, INVALID_ARGUMENT);

    // Convert the value to a string or just copy it if it is already
    // a string.
    if (value->type == BAMP_TYPE_STRING)
    {
        const size_t len = strlen(value->value.str) + 1;
        *str = (char*)malloc(len * sizeof(char));
        BAMP_RC_ASSERT(*str != NULL, OUT_OF_MEMORY);
        strcpy(*str, value->value.str);
    }
    else
    {
        const size_t len = bamp_value_type_string_length[value->type];
        const char*  fmt = bamp_value_type_string_format[value->type];
        *str = (char*)malloc(len * sizeof(char));
        BAMP_RC_ASSERT(*str != NULL, OUT_OF_MEMORY);
        snprintf(*str, len, fmt, value->value);
    }

    return BAMP_RC_OK;    
}


bamp_rc bamp_value_new_from_str(bamp_value** value, const char* str,
                                bamp_type type)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(str   != NULL, INVALID_ARGUMENT);
    
    // Allocate the structure
    *value = malloc(sizeof(bamp_value));
    BAMP_RC_ASSERT(*value != NULL, OUT_OF_MEMORY);

    // Initialize the structure
    (*value)->type = type;
    (*value)->refs = 1;

    if (type == BAMP_TYPE_STRING)
    {
        const size_t len = strlen(str) + 1;
        (*value)->value.str = (char*)malloc(len * sizeof(char));
        BAMP_RC_ASSERT((*value)->value.str != NULL, OUT_OF_MEMORY);
        strcpy((*value)->value.str, str);
    }
    else if (type != BAMP_TYPE_NULL)
    {
        const char* fmt = bamp_value_type_string_format[type];
        BAMP_RC_ASSERT(sscanf(str, fmt, (*value)->value) != 0, TYPE_MISMATCH);
    }

    return BAMP_RC_OK;    
}


bamp_rc bamp_value_new(bamp_value** value)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);

    // Allocate the structure
    *value = malloc(sizeof(bamp_value));
    BAMP_RC_ASSERT(*value != NULL, OUT_OF_MEMORY);

    // Initialize the structure
    (*value)->type = BAMP_TYPE_NULL;
    (*value)->refs = 1;

    return BAMP_RC_OK;
}


bamp_rc bamp_value_clone(bamp_value** dst, const bamp_value* src)
{
    BAMP_RC_ASSERT(dst != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(src != NULL, INVALID_ARGUMENT);

    // Don't try to copy a value into itself
    if (*dst == src) return BAMP_RC_OK;

    // Allocate the structure
    *dst = malloc(sizeof(bamp_value));
    BAMP_RC_ASSERT(*dst != NULL, OUT_OF_MEMORY);

    // Copy the type
    (*dst)->type = src->type;

    // Initialize the reference counter
    (*dst)->refs = 1;

    // Copy the value. If it is a string, allocate a new string to
    // hold the copy. Otherwise, just copy the value.
    if (src->type == BAMP_TYPE_STRING)
    {
        if (src->value.str != NULL)
        {
            (*dst)->value.str = malloc(strlen(src->value.str)+1);
            BAMP_RC_ASSERT((*dst)->value.str != NULL, OUT_OF_MEMORY);
            strcpy((*dst)->value.str, src->value.str);
        }
        else
        {
            (*dst)->value.str = NULL;
        }
    }
    else
    {
        memcpy(&(*dst)->value, &src->value, sizeof(src->value));
    }

    return BAMP_RC_OK;
}


bamp_rc bamp_value_ref(bamp_value* value)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    ++(value->refs);
    return BAMP_RC_OK;
}


bamp_rc bamp_value_unref(bamp_value* value)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    --(value->refs);
    
    // Free the value if required
    if (value->refs == 0)
    {
        if (value->type == BAMP_TYPE_STRING && value->value.str != NULL)
            free(value->value.str);
        free(value);
    }

    return BAMP_RC_OK;
}


bamp_rc bamp_value_set_null(bamp_value* value)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    
    // Free the value, if required
    if (value->type == BAMP_TYPE_STRING && value->value.str != NULL)
    {
        free(value->value.str);
    }

    // Set the value to null
    value->type = BAMP_TYPE_NULL;

    return BAMP_RC_OK;
}


bamp_rc bamp_value_set_str(bamp_value* value, const char* str)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    
    // Free the value, if required
    if (value->type == BAMP_TYPE_STRING && value->value.str != NULL)
    {
        free(value->value.str);
    }

    // Set the type
    value->type = BAMP_TYPE_STRING;

    // Copy the string
    if (str != NULL)
    {
        value->value.str = malloc(strlen(str)+1);
        BAMP_RC_ASSERT(value->value.str != NULL, OUT_OF_MEMORY);
        strcpy(value->value.str, str);
    }
    else
    {
        value->value.str = NULL;
    }

    return BAMP_RC_OK;
}

BAMP_VALUE_GET_SET_FUNC(uint8_t,  u8,  U8)
BAMP_VALUE_GET_SET_FUNC(uint16_t, u16, U16)
BAMP_VALUE_GET_SET_FUNC(uint32_t, u32, U32)
BAMP_VALUE_GET_SET_FUNC(uint64_t, u64, U64)

BAMP_VALUE_GET_SET_FUNC(int8_t,  i8,  I8)
BAMP_VALUE_GET_SET_FUNC(int16_t, i16, I16)
BAMP_VALUE_GET_SET_FUNC(int32_t, i32, I32)
BAMP_VALUE_GET_SET_FUNC(int64_t, i64, I64)

BAMP_VALUE_GET_SET_FUNC(float,  f32, F32)
BAMP_VALUE_GET_SET_FUNC(double, f64, F64)

bamp_rc bamp_value_get_type(const bamp_value* value, bamp_type* type)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(type  != NULL, INVALID_ARGUMENT);
    *type = value->type;
    return BAMP_RC_OK;
}


bamp_rc bamp_value_is_null(const bamp_value* value, int* is_null)
{
    BAMP_RC_ASSERT(value   != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(is_null != NULL, INVALID_ARGUMENT);
    *is_null = value->type == BAMP_TYPE_NULL ? 1 : 0;
    return BAMP_RC_OK;
}


bamp_rc bamp_value_get_str(const bamp_value* value, char** str)
{
    BAMP_RC_ASSERT(value != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(str   != NULL, INVALID_ARGUMENT);
    BAMP_RC_ASSERT(value->type == BAMP_TYPE_STRING, TYPE_MISMATCH);
    *str = value->value.str;
    return BAMP_RC_OK;
}
